import idljGenerated.*;
import middleware.*;
import java.util.*;

public class SolucionesReinas extends ISolucionesReinasPOA
    implements CallbackNuevaSolucion
{
    IDirectorioColaboradores directorioColaboradores;
    ISolucionesReinas solucionesReinas;

    Set<String> colaboradoresOcupados;
    Vector<TableroReinas> soluciones;
    Vector<TableroReinas> solucionesParciales;
    
    int tamanyo;
    long tiempo;

    // Constructor por defecto
    public SolucionesReinas()
    {
        colaboradoresOcupados = new HashSet<String>();
        soluciones = new Vector<TableroReinas>();
        solucionesParciales = new Vector<TableroReinas>();
        tamanyo = 0;
        tiempo = 0;
    }

    private synchronized void anyadirSolucion(TableroReinas s)
    {
        soluciones.add(s);
    }

    private synchronized void anyadirSolucionParcial(TableroReinas s)
    {
        solucionesParciales.add(s);
    }

    public synchronized TableroReinas siguienteSolucionParcial()
    {
        return solucionesParciales.isEmpty() ? null : solucionesParciales.remove(0);
    }

    private synchronized boolean anyadirColaboradorOcupado(String colaborador)
    {
        if (!colaboradoresOcupados.contains(colaborador))
        {
            colaboradoresOcupados.add(colaborador);
            return true;
        }

        return false;
    }

    private synchronized void eliminarColaboradorOcupado(String colaborador)
    {
        colaboradoresOcupados.remove(colaborador);
    }

    private synchronized boolean terminado(List<Hilo> hilos)
    {
        return colaboradoresOcupados.isEmpty() && !quedanHilosVivos(hilos);
    }

    @Override
    public void recibeSoluciones(int[][] l, IColaboradorReinas colaborador)
    {
        // System.out.println("Recibo soluciones del colaborador " + colaborador.nombre());

        // Anyadimos nuevas soluciones
        for(int i = 0 ; i < l.length ; i++)
        {
            TableroReinas t = new TableroReinas(l[i], false);
            anyadirSolucion(t);
            // System.out.println("Solucion: " + t);
        }

        // Intentamos sacar una solucion de la lista de soluciones parciales
        TableroReinas solucionParcial = siguienteSolucionParcial();
        
        // Si no hay mas soluciones que resolver
        if(solucionParcial == null)
        {
            // Liberamos al colaborador
            eliminarColaboradorOcupado(colaborador.nombre());
        }
        else
        {
            // Le mandamos nuevas tareas
            enviaTrabajo(colaborador, solucionParcial.toArray());
        }
    }

    // Tratar nueva solucion que puede ser parcial o final
    public void tratarNuevaSolucion(TableroReinas solucion)
    {
        // Si es una solucion final, la anyadimos a la lista
        if (solucion.getColumnasCompletadas() == solucion.getTamanyo())
        {
            anyadirSolucion(solucion.clone());
            return;
        }

        // Si es una solucion parcial y hay directorio de colaboradores
        // buscamos un colaborador ocioso.
        if (directorioColaboradores != null)
        {
            for(IColaboradorReinas colaborador :
                    directorioColaboradores.obtenerListaColaboradores())
            {
                String nombre = colaborador.nombre();

                // Si podemos anyadir colaborador es porque esta libre
                if(anyadirColaboradorOcupado(nombre))
                {
                    // le enviamos una nueva solucion para que la acabe
                    enviaTrabajo(colaborador, solucion.toArray());

                    return;
                }
            }
        }

        // Si no hay ningun colaborador libre, metemos la solucion parcial en la lista
        anyadirSolucionParcial(solucion.clone());
    }

    private void enviaTrabajo(IColaboradorReinas colaborador, int[] solucion)
    {
        // System.out.println("Envio trabajo al colaborador " + colaborador.nombre());
        colaborador.evaluaSubArbol(solucion, solucionesReinas);
    }

    // Funcion que analiza los colaboradores y el numero de procesadores
    // procesadores y elige hasta donde tiene que desarrollar el hilo principal.
    private int getMaxColumn()
    {
        return 2;
    }

    // Cronometra la busqueda de soluciones
    private void buscaSoluciones()
    {
        System.out.println("Comienza la busqueda de soluciones.");

        // Antes que nada: ponemos el crono en marcha:
        long inicio = System.currentTimeMillis();

        // Expandimos el arbol de busqueda hasta nivel maxColumn
        TableroReinas solucion = new TableroReinas(tamanyo);
        int maxColumn = getMaxColumn();
        AnalizaColumna.func(maxColumn, solucion, this);

        // Hay que vaciar la lista de soluciones parciales, de eso ya se estan
        // ocupando los colaboradores, pero para para ayudar creamos unos hilos.
        int nProcesadores = Runtime.getRuntime().availableProcessors();
        Vector<Hilo> hilos = new Vector<Hilo>();
        for (int i = 0; i < nProcesadores - 1; i++)
        {
            Hilo hilo = new Hilo(this);
            hilo.start();
            hilos.add(hilo);
        }

        // Y nosotros tambien trabajamos.
        resolverSolucionesParciales();

        // Esperando por colaboradores ocupados y por otros hilos
        while(!terminado(hilos))
        {
            try { Thread.currentThread().sleep(10); }
            catch(InterruptedException ie) {}
        }

        // Y hemos terminado. Paramos cronometro:
        tiempo = System.currentTimeMillis()-inicio;

        System.out.println("Encontradas " + soluciones.size() +
                " soluciones en " + tiempo + "ms.");
    }
    
    private boolean quedanHilosVivos(List<Hilo> hilos)
    {
        for (Hilo hilo : hilos)
        {
            if (hilo.isAlive())
                return true;
        }
        
        return false;
    }

    // Resuelve soluciones parciales de la lista de soluciones parciales mientras queden.
    public void resolverSolucionesParciales()
    {
        TableroReinas solucionParcial = siguienteSolucionParcial();
        while(solucionParcial != null)
        {
            AnalizaColumna.func(solucionParcial.getTamanyo(), solucionParcial, this);
            solucionParcial = siguienteSolucionParcial();
        }
    }

    public static void main(String[] args)
    {
        if (args.length < 1)
        {
            System.out.println("java SolucionesReinas num");
            System.out.println("Siendo num el tamanyo del tablero a procesar.");
            return;
        }

        // Inicializar middleware:
        JavaORB mdlw = new JavaORB();
		mdlw.nombreSN = "pcdultimate";
		mdlw.opcionesLC = args;

        // A partir de aqui, codigo es independiente de middleware!
        Middleware.inicializar(mdlw);

        // Crear sirviente
        SolucionesReinas sirviente = new SolucionesReinas();

        // Registrarse
        ISolucionesReinas solucionesReinas =
            (ISolucionesReinas) Middleware.registrar(sirviente, "ISolucionesReinas");
        System.out.println("Arrancando programa principal");

        // Localizar el directorio de colaboradores
        IDirectorioColaboradores directorioColaboradores = 
            (IDirectorioColaboradores)Middleware.localizar("directorioColaboradores", "IDirectorioColaboradores");

        // Si no se pudo localizar
        if (directorioColaboradores == null)
            System.out.println("Aviso: No se encontro directorio de colaboradores.");

        sirviente.solucionesReinas = solucionesReinas;
        sirviente.directorioColaboradores = directorioColaboradores;
        sirviente.tamanyo = new Integer(args[0]);
        sirviente.buscaSoluciones();
    }
    
    private class Hilo extends Thread
    {
        private SolucionesReinas solucionesReinas;
    
        public Hilo(SolucionesReinas solucionesReinas)
        {
            this.solucionesReinas = solucionesReinas;
        }
    
        public void run()
        {
            solucionesReinas.resolverSolucionesParciales();
        }    
    }
}